package com.hmsm.server.util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipOutputStream;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.commons.lang3.StringUtils;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Zip;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

/**
 * @Description:把目录压缩为Zip文件
 * @ClassName :zip.test.ZipUtil.java
 * @Author :fengt
 * @CreateDate :2013-7-9 下午12:39:20
 **/
public class ZipUtil {
	public static final String PRIVATE_KEY = "hmsm";// 密钥

	private File zipFile;
	private static String type = "AES";

	public ZipUtil(String pathName) {
		zipFile = new File(pathName);
	}
	
	/**
	 * 递归压缩文件夹
	 * 
	 * @param srcRootDir
	 *            压缩文件夹根目录的子路径
	 * @param file
	 *            当前递归压缩的文件或目录对象
	 * @param zos
	 *            压缩文件存储对象
	 * @throws Exception
	 */
	private static void zip(String srcRootDir, File file, ZipOutputStream zos) throws Exception {
		if (file == null) {
			return;
		}

		// 如果是文件，则直接压缩该文件
		if (file.isFile()) {
			int count, bufferLen = 1024;
			byte data[] = new byte[bufferLen];

			// 获取文件相对于压缩文件夹根目录的子路径
			String subPath = file.getAbsolutePath();
			int index = subPath.indexOf(srcRootDir);
			if (index != -1) {
				subPath = subPath.substring(srcRootDir.length() + File.separator.length());
			}
			ZipEntry entry = new ZipEntry(subPath);
			zos.putNextEntry(entry);
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
			while ((count = bis.read(data, 0, bufferLen)) != -1) {
				zos.write(data, 0, count);
			}
			bis.close();
			zos.closeEntry();
		}
		// 如果是目录，则压缩整个目录
		else {
			// 压缩目录中的文件或子目录
			File[] childFileList = file.listFiles();
			for (int n = 0; n < childFileList.length; n++) {
				childFileList[n].getAbsolutePath().indexOf(file.getAbsolutePath());
				zip(srcRootDir, childFileList[n], zos);
			}
		}
	}

	/**
	 * 对文件或文件目录进行压缩
	 * 
	 * @param srcPath
	 *            要压缩的源文件路径。如果压缩一个文件，则为该文件的全路径；如果压缩一个目录，则为该目录的顶层目录路径
	 * @param zipPath
	 *            压缩文件保存的路径。注意：zipPath不能是srcPath路径下的子文件夹
	 * @param zipFileName
	 *            压缩文件名
	 * @throws Exception
	 */
	public static void zip(String srcPath, String zipPath, String zipFileName) throws Exception {
		if (StringUtils.isEmpty(srcPath) || StringUtils.isEmpty(zipPath) || StringUtils.isEmpty(zipFileName)) {
			throw new Exception("参数不足！");
		}
		CheckedOutputStream cos = null;
		ZipOutputStream zos = null;
		try {
			File srcFile = new File(srcPath);

			// 判断压缩文件保存的路径是否为源文件路径的子文件夹，如果是，则抛出异常（防止无限递归压缩的发生）
			if (srcFile.isDirectory() && zipPath.indexOf(srcPath) != -1) {
				throw new Exception("参数无效，zipPath must not be the child directory of srcPath.");
			}

			// 判断压缩文件保存的路径是否存在，如果不存在，则创建目录
			File zipDir = new File(zipPath);
			if (!zipDir.exists() || !zipDir.isDirectory()) {
				zipDir.mkdirs();
			}

			// 创建压缩文件保存的文件对象
			String zipFilePath = zipPath + File.separator + zipFileName;
			File zipFile = new File(zipFilePath);
			if (zipFile.exists()) {
				// 检测文件是否允许删除，如果不允许删除，将会抛出SecurityException
				SecurityManager securityManager = new SecurityManager();
				securityManager.checkDelete(zipFilePath);
				// 删除已存在的目标文件
				zipFile.delete();
			}

			cos = new CheckedOutputStream(new FileOutputStream(zipFile), new CRC32());
			zos = new ZipOutputStream(cos);

			// 如果只是压缩一个文件，则需要截取该文件的父目录
			String srcRootDir = srcPath;
			if (srcFile.isFile()) {
				int index = srcPath.lastIndexOf(File.separator);
				if (index != -1) {
					srcRootDir = srcPath.substring(0, index);
				}
			}
			// 调用递归压缩方法进行目录或文件压缩
			zip(new File(srcRootDir).getAbsolutePath(), srcFile, zos);
			zos.flush();
		} catch (Exception e) {
			throw e;
		} finally {
			try {
				if (zos != null) {
					zos.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @Description:压缩文件夹
	 * @MethodName :compress
	 * @Author :fengt
	 * @CreateDate :2013-7-9 下午05:06:17
	 * @param srcPathName
	 *            文件夹路径
	 **/
	public void compress(String srcPathName) {
		File srcdir = new File(srcPathName);
		if (!srcdir.exists()) {
			throw new RuntimeException(srcPathName + "不存在！");
		}
		Project prj = new Project();
		Zip zip = new Zip();
		zip.setProject(prj);
		zip.setDestFile(zipFile);
		FileSet fileSet = new FileSet();
		fileSet.setProject(prj);
		fileSet.setDir(srcdir);
		// fileSet.setIncludes("**/*.java"); 包括哪些文件或文件夹
		// eg:zip.setIncludes("*.java");
		// fileSet.setExcludes(...); 排除哪些文件或文件夹
		zip.addFileset(fileSet);
		zip.execute();
	}

	@SuppressWarnings("unchecked")
	public static void unzip(String zipFilePath, String unzipDirectory)
			throws Exception {
		// 定义输入输出流对象
		InputStream input = null;
		OutputStream output = null;
		try {
			// 创建文件对象
			File file = new File(zipFilePath);
			// 创建zip文件对象
			ZipFile zipFile = new ZipFile(file);
			// 创建本zip文件解压目录
			String name = file.getName().substring(0,
					file.getName().lastIndexOf("."));
			File unzipFile = new File(unzipDirectory + "/" + name);
			if (unzipFile.exists()){
				unzipFile.delete();
			}
			unzipFile.mkdir();
			// 得到zip文件条目枚举对象
			Enumeration zipEnum = zipFile.getEntries();
			// 定义对象
			ZipEntry entry = null;
			String entryName = null, path = null;
			String names[] = null;
			int length;
			// 循环读取条目
			while (zipEnum.hasMoreElements()) {
				// 得到当前条目
				entry = (ZipEntry) zipEnum.nextElement();
				entryName = new String(entry.getName());
				// 用/分隔条目名称
				names = entryName.split("\\/");
				length = names.length;
				path = unzipFile.getAbsolutePath();
				for (int v = 0; v < length; v++) {
					if (v < length - 1) // 最后一个目录之前的目录
						createFile(path += "/" + names[v] + "/");
					else { // 最后一个
						if (entryName.endsWith("/")){ // 为目录,则创建文件夹
							createFile(unzipFile.getAbsolutePath() + "/"+ entryName);
						}else { // 为文件,则输出到文件
							input = zipFile.getInputStream(entry);
							output = new FileOutputStream(new File(unzipFile
									.getAbsolutePath()
									+ "/" + entryName));
							byte[] buffer = new byte[1024 * 8];
							int readLen = 0;
							while ((readLen = input.read(buffer, 0, 1024 * 8)) != -1){
								output.write(buffer, 0, readLen);
							}

						}
					}
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			// 关闭流
			if (input != null){
				input.close();
			}
			if (output != null) {
				output.flush();
				output.close();
			}
		}
	}
	
	/**
	 *  @Description:创建文件夹
	 *  @MethodName :createFile
	 *  @Author     :fengt
	 *  @CreateDate :2013-7-10 下午03:10:40 
	 *  @param filePath
	 *  @throws IOException
	 **/
	private static void createFile(String filePath) throws IOException {
		File file = new File(filePath);
		if (file.exists()) {
			file.delete();
		}
		file.mkdirs();
	}

	/**
	 * @Description:加密文件
	 * @MethodName :encrypt
	 * @Author :fengtao
	 * @CreateDate :2013-7-10 上午11:38:15
	 * @param srcFile
	 *            加密前
	 * @param destFile
	 *            加密后
	 * @param privateKey
	 *            密钥
	 **/
	public static void encrypt(String srcFile, String destFile,
			String privateKey) {
		try {
			Key key = getKey(privateKey);
			Cipher cipher = Cipher.getInstance(type + "/ECB/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, key);

			FileInputStream fis = null;
			FileOutputStream fos = null;
			try {
				fis = new FileInputStream(srcFile);
				fos = new FileOutputStream(mkdirFiles(destFile));
				crypt(fis, fos, cipher);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				if (fis != null) {
					fis.close();
				}
				if (fos != null) {
					fos.close();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @Description:解密
	 * @MethodName :decrypt
	 * @Author :fengtao
	 * @CreateDate :2013-7-10 上午11:58:50
	 * @param srcFile
	 *            解密前
	 * @param destFile
	 *            解密后
	 * @param privateKey
	 *            密钥
	 * @throws GeneralSecurityException
	 * @throws IOException
	 **/
	public static boolean decrypt(String srcFile, String destFile,
			String privateKey) {
		try {
			Key key = getKey(privateKey);
			Cipher cipher = Cipher.getInstance(type + "/ECB/PKCS5Padding");
			cipher.init(Cipher.DECRYPT_MODE, key);
			FileInputStream fis = null;
			FileOutputStream fos = null;
			try {
				fis = new FileInputStream(srcFile);
				fos = new FileOutputStream(mkdirFiles(destFile));
				crypt(fis, fos, cipher);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				if (fis != null) {
					fis.close();
				}
				if (fos != null) {
					fos.close();
				}
			}
		} catch (Exception e) {
			deleteFile(destFile);// 解压失败，就把解密后文件删除
			return false;
		}
		return true;
	}

	/**
	 * @Description:删除单个文件
	 * @MethodName :deleteFile
	 * @Author :fengt
	 * @CreateDate :2013-7-10 下午01:28:23
	 * @param sPath
	 * @return
	 **/
	public static void deleteFile(String filePath) {
		File file = new File(filePath);
		// 路径为文件且不为空则进行删除
		if (file.isFile() && file.exists()) {
			file.delete();
		}
	}

	/**
	 * @Description:要创建的文件路径
	 * @MethodName :mkdirFiles
	 * @Author :fengtao
	 * @CreateDate :2013-7-10 上午11:49:51
	 * @param filePath
	 * @return
	 * @throws IOException
	 **/
	private static File mkdirFiles(String filePath) throws IOException {
		File file = new File(filePath);
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		file.createNewFile();
		return file;
	}

	/**
	 * @Description:加密解密流
	 * @MethodName :crypt
	 * @Author :fengtao
	 * @CreateDate :2013-7-10 上午11:52:52
	 * @param in
	 *            加密解密前的流
	 * @param out
	 *            加密解密后的流
	 * @param cipher
	 *            加密/解密
	 * @throws IOException
	 * @throws GeneralSecurityException
	 **/
	private static void crypt(InputStream in, OutputStream out, Cipher cipher)
			throws IOException, GeneralSecurityException {
		int blockSize = cipher.getBlockSize() * 1000;
		int outputSize = cipher.getOutputSize(blockSize);
		byte[] inBytes = new byte[blockSize];
		byte[] outBytes = new byte[outputSize];
		int inLength = 0;
		boolean more = true;
		while (more) {
			inLength = in.read(inBytes);
			if (inLength == blockSize) {
				int outLength = cipher.update(inBytes, 0, blockSize, outBytes);
				out.write(outBytes, 0, outLength);
			} else {
				more = false;
			}
		}
		if (inLength > 0) {
			outBytes = cipher.doFinal(inBytes, 0, inLength);
		} else {
			outBytes = cipher.doFinal();
		}
		out.write(outBytes);
	}

	/**
	 * @Description:生成指定字符串密钥
	 * @MethodName :getKey
	 * @Author :fengtao
	 * @CreateDate :2013-7-10 上午11:44:35
	 * @param privateKey
	 *            要生成密钥的字符串
	 * @return
	 * @throws GeneralSecurityException
	 **/
	private static Key getKey(String privateKey)
			throws GeneralSecurityException {
		KeyGenerator kgen = KeyGenerator.getInstance(type);
		kgen.init(128, new SecureRandom(privateKey.getBytes()));
		SecretKey secretKey = kgen.generateKey();
		return secretKey;
	}
}
